package com.open.rbac.service.id.impl;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.stream.Collectors;

import org.springframework.data.domain.*;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.Predicate;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import com.open.center.common.constant.OpenField;
import com.open.center.common.exception.OpenException;
import com.open.center.common.util.CopyUtils;
import com.open.center.jpa.model.entity.IdEntity;
import com.open.center.jpa.util.SpecificationUtils;
import com.open.rbac.model.dto.id.IdentityDto;
import com.open.rbac.model.dto.id.IdentityOrganizationRelDto;
import com.open.rbac.model.dto.id.IdentityRelDto;
import com.open.rbac.model.entity.id.Identity;
import com.open.rbac.model.query.IdentityQuery;
import com.open.rbac.model.vo.id.IdentityOrganizationRelVo;
import com.open.rbac.model.vo.id.IdentityRelVo;
import com.open.rbac.model.vo.id.IdentityVo;
import com.open.rbac.service.id.IdentityOrganizationRelService;
import com.open.rbac.service.id.IdentityRelService;
import com.open.rbac.service.id.IdentityService;
import com.open.rbac.service.id.repository.IdentityRepository;
import com.open.rbac.service.sys.IOrganizationService;


@Slf4j
@RequiredArgsConstructor
@Service
public class IdentityServiceImpl implements IdentityService {

    private final IdentityRepository identityRepository;

    private final IdentityRelService identityRelService;

    private final IdentityOrganizationRelService identityOrganizationRelService;

    private final IOrganizationService organizationService;

    @Transactional
    @Override
    public IdentityDto create(IdentityVo identityVo) {
        String idMd5 = DigestUtils.md5DigestAsHex(identityVo.getIdNumber().getBytes(StandardCharsets.UTF_8));
        List<Identity> md5Identitys = identityRepository.findByIdMd5(idMd5);
        if (!CollectionUtils.isEmpty(md5Identitys)) {
            IdentityDto identityDto = CopyUtils.copy(md5Identitys.getFirst(), IdentityDto.class);
            log.error("{}, {}, {}, Id信息已存在", identityDto.getName(), identityDto.getGender(), identityDto.getIdNumber());
            throw new OpenException(md5Identitys.getFirst().getName() + " Id信息已存在");
        }
        Identity identity = CopyUtils.copy(identityVo, Identity.class);
        identity.setIdMd5(idMd5);

        createRel(identity.getId(), identityVo.getRel());
        createOrganizationRels(identity.getId(), identityVo.getOrganizationIds());
        identityRepository.save(identity);
        return CopyUtils.copy(identity, IdentityDto.class);
    }

    @Transactional
    @Override
    public IdentityDto update(IdentityVo identityVo) {
        updateRel(identityVo.getId(), identityVo.getRel());
        updateOrganizationRels(identityVo.getId(), identityVo.getOrganizationIds());
        Optional<Identity> optional = identityRepository.findById(identityVo.getId());
        return optional.map(id -> {
            CopyUtils.copyNonNull(identityVo, id);
            identityRepository.save(id);
            return CopyUtils.copy(id, IdentityDto.class);
        }).orElse(null);
    }

    protected void updateRel(final String id, IdentityRelVo relVo) {
        if (relVo != null) {
            identityRelService.deleteReallyByRelIdentityId(id, relVo.getRelIdentityId());
            createRel(id, relVo);
        }
    }

    protected void updateOrganizationRels(final String id, List<String> organizationIds) {
        identityOrganizationRelService.deleteReallyByIdentityId(id);
        createOrganizationRels(id, organizationIds);
    }

    protected void createRel(String relId, IdentityRelVo relVo) {
        if (relVo != null) {
            relVo.setRelIdentityId(relId);
            identityRelService.create(List.of(relVo));
        }
    }

    protected void createOrganizationRels(final String id, List<String> organizationIds) {
        if (!CollectionUtils.isEmpty(organizationIds)) {
            List<IdentityOrganizationRelVo> rels = organizationIds.stream()
                .map(organizationId -> IdentityOrganizationRelVo.of(id, organizationId)).toList();
            identityOrganizationRelService.create(rels);
        }
    }


    @Transactional
    @Override
    public void deleteById(String id) {
        identityRepository.deleteById(id);
        identityRelService.deleteReallyByIdentityId(id);
        identityOrganizationRelService.deleteReallyByIdentityId(id);
    }

    @Override
    public void deleteByIds(List<String> ids) {
        identityRepository.deleteByIds(ids);
        identityRelService.deleteReallyByIdentityIds(ids);
        identityOrganizationRelService.deleteReallyByIdentityIds(ids);
    }

    @Override
    public IdentityDto findById(String id) {
        Optional<Identity> optional = identityRepository.findById(id);
        return optional.map(d -> CopyUtils.copy(d, IdentityDto.class)).orElse(null);
    }

    @Override
    public List<IdentityDto> findByIdNumber(String idNumber) {
        String idMd5 = DigestUtils.md5DigestAsHex(idNumber.getBytes(StandardCharsets.UTF_8));
        return CopyUtils.listCopy(identityRepository.findByIdMd5(idMd5), IdentityDto.class);
    }

    @Override
    public void createRelIdentity(IdentityRelVo identityRelVo) {
        updateRel(identityRelVo.getIdentityId(), identityRelVo);
    }

    @Override
    public void createOrganizations(String id, List<String> organizationIds) {
        updateOrganizationRels(id, organizationIds);
    }

    @Override
    public Page<IdentityDto> page(IdentityQuery query) {
        // 排序字段需要唯一，如果存在一样的数据，排序则不稳定(本例：默认为createTime,很多条数据createTime都是一样的)
        Sort finalSort = query.getRequest().getSort()
            .and(Sort.by(Sort.Direction.ASC, OpenField.ID.getId()));
        Pageable finalPageable = PageRequest.of(query.getRequest().getPageNumber(),
                query.getRequest().getPageSize(), finalSort);
        Page<Identity> page = identityRepository.findAll(putSpecification(query), finalPageable);
        return new PageImpl<>(copyIdentityDtos(page.getContent()),
            page.getPageable(), page.getTotalElements());
    }

    private Specification<Identity> putSpecification(IdentityQuery query) {
        Specification<Identity> specification = SpecificationUtils.specification(query);
        if (StringUtils.hasText(query.getOrganizationId())) {
            List<String> identityIdIds = new ArrayList<>();
            List<String> organizationIds = organizationService.listChildIdsById(query.getOrganizationId());
            List<IdentityOrganizationRelDto> organizationRelDtos = identityOrganizationRelService.listIdentityIdIds(organizationIds);
            if (!CollectionUtils.isEmpty(organizationIds)) {
                identityIdIds.addAll(organizationRelDtos.stream().map(IdentityOrganizationRelDto::getIdentityId).toList());
            }
            if (CollectionUtils.isEmpty(identityIdIds)) {
                identityIdIds.add("");
            }
            Specification<Identity> inSpecification = (root, q, cb) -> {
                List<Predicate> predicates = new ArrayList<>();
                CriteriaBuilder.In<String> in = cb.in(root.get("id"));
                for (String identityId : identityIdIds) {
                    in.value(identityId);
                }
                predicates.add(in);
                Predicate[] p = new Predicate[0];
                return cb.and(predicates.toArray(p));
            };
            specification = specification.and(inSpecification);
        }
        return specification;
    }

    public List<IdentityDto> copyIdentityDtos(List<Identity> identitys) {
        if (CollectionUtils.isEmpty(identitys)) {
            return Collections.emptyList();
        }
        List<String> ids = identitys.stream().map(IdEntity::getId).toList();
        List<IdentityRelDto> identityRelDtos = identityRelService.listByIdentityIds(ids);
        Map<String, List<IdentityRelDto>> mapIdentityRelDtos = null;
        if (!CollectionUtils.isEmpty(identityRelDtos)) {
            mapIdentityRelDtos = identityRelDtos.stream()
                .collect(Collectors.groupingBy(IdentityRelDto::getIdentityId));
        }
        List<IdentityOrganizationRelDto> organizationRelDtos = identityOrganizationRelService
            .listByIdentityIdIds(ids);
        Map<String, List<IdentityOrganizationRelDto>> mapIdentityOrganizationRelDtos = null;
        if (!CollectionUtils.isEmpty(organizationRelDtos)) {
            mapIdentityOrganizationRelDtos = organizationRelDtos.stream()
                .collect(Collectors.groupingBy(IdentityOrganizationRelDto::getIdentityId));
        }
        List<IdentityDto> list = new ArrayList<>();
        for (Identity identity : identitys) {
            IdentityDto identityDto = CopyUtils.copy(identity, IdentityDto.class);
            identityDto.setIdNumberText(identity.getIdNumber());
            identityDto.setPhoneText(identity.getPhone());

            if (!CollectionUtils.isEmpty(mapIdentityRelDtos)) {
                identityDto.setIdentityRels(mapIdentityRelDtos.get(identity.getId()));
            }
            if (!CollectionUtils.isEmpty(mapIdentityOrganizationRelDtos)) {
                identityDto.setOrganizationRels(mapIdentityOrganizationRelDtos.get(identity.getId()));
            }
            list.add(identityDto);
        }
        return list;
    }
}
